/**
 * Copyright 2013-2022 the original author or authors from the Jeddict project (https://jeddict.github.io/).
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.jeddict.reveng.doc;

import io.github.jeddict.jaxb.spec.JaxbVariableType;
import static io.github.jeddict.jcode.util.StringHelper.camelCase;
import static io.github.jeddict.jcode.util.StringHelper.firstLower;
import static io.github.jeddict.jcode.util.StringHelper.firstUpper;
import io.github.jeddict.jpa.spec.Basic;
import io.github.jeddict.jpa.spec.ElementCollection;
import io.github.jeddict.jpa.spec.Entity;
import io.github.jeddict.jpa.spec.EntityMappings;
import io.github.jeddict.jpa.spec.OneToMany;
import io.github.jeddict.jpa.spec.OneToOne;
import io.github.jeddict.jpa.spec.bean.BeanAttribute;
import io.github.jeddict.jpa.spec.bean.BeanClass;
import io.github.jeddict.jpa.spec.bean.BeanCollectionAttribute;
import io.github.jeddict.jpa.spec.bean.OneToManyAssociation;
import io.github.jeddict.jpa.spec.bean.OneToOneAssociation;
import io.github.jeddict.jpa.spec.extend.Attribute;
import java.io.IOException;
import java.io.Reader;
import java.util.function.Consumer;
import static org.netbeans.modeler.core.NBModelerUtil.getAutoGeneratedStringId;
import org.netbeans.modeler.core.exception.ProcessInterruptedException;

/**
 *
 * @author jGauravGupta
 */
public abstract class DocParser {

    protected final Consumer<String> reporter;
    protected boolean jpaSupport, jsonbSupport, jaxbSupport;

    public DocParser(Consumer<String> reporter, boolean jpaSupport, boolean jsonbSupport, boolean jaxbSupport) {
        this.reporter = reporter;
        this.jpaSupport = jpaSupport;
        this.jsonbSupport = jsonbSupport;
        this.jaxbSupport = jaxbSupport;
    }

    public abstract EntityMappings generateModel(EntityMappings entityMappings, Reader reader) throws IOException, ProcessInterruptedException;

    protected BeanClass createBeanClass(EntityMappings entityMappings, String className) {
        return entityMappings.findBeanClass(firstUpper(camelCase(className))).orElseGet(() -> {
            BeanClass beanClass = new BeanClass();
            beanClass.setId(getAutoGeneratedStringId());
            beanClass.setClazz(firstUpper(camelCase(className)));
            entityMappings.addBeanClass(beanClass);
            return beanClass;
        });
    }

    protected Entity createEntity(EntityMappings entityMappings, String className) {
        return entityMappings.findEntity(firstUpper(camelCase(className))).orElseGet(() -> {
            Entity entity = new Entity();
            entity.setId(getAutoGeneratedStringId());
            entity.setClazz(firstUpper(camelCase(className)));
            entityMappings.addEntity(entity);
            return entity;
        });
    }

    protected BeanAttribute createBeanAttribute(BeanClass beanClass, String attributeType, String key) {
        String name = firstLower(camelCase(key));
        BeanAttribute attribute = beanClass.getAttributes()
                .findAllAttribute(name)
                .stream()
                .filter(attr -> attr instanceof BeanAttribute)
                .map(attr -> (BeanAttribute) attr)
                .findAny()
                .orElse(null);
        if (attribute == null) {
            attribute = new BeanAttribute();
            attribute.setAttributeType(attributeType);
            beanClass.getAttributes().addBasic(attribute);
            if (jaxbSupport) {
                attribute.setJaxbVariableType(JaxbVariableType.XML_ATTRIBUTE);
            }
            updateAttributeName(attribute, key);
        }
        return attribute;
    }

    protected BeanCollectionAttribute createBeanCollection(BeanClass beanClass, String attributeType, String key) {
        String name = firstLower(camelCase(key));
        BeanCollectionAttribute attribute = beanClass.getAttributes()
                .findAllAttribute(name)
                .stream()
                .filter(attr -> attr instanceof BeanCollectionAttribute)
                .map(attr -> (BeanCollectionAttribute) attr)
                .findAny()
                .orElse(null);
        if (attribute == null) {
            attribute = new BeanCollectionAttribute();
            attribute.setAttributeType(attributeType);
            beanClass.getAttributes().addElementCollection(attribute);
            if (jaxbSupport) {
                attribute.setJaxbVariableType(JaxbVariableType.XML_ELEMENT);
            }
            updateAttributeName(attribute, name);
        }
        return attribute;
    }

    protected OneToOneAssociation createOneToOneAssociation(BeanClass beanClass, BeanClass connectedClass, String key) {
        String name = firstLower(camelCase(key));
        OneToOneAssociation attribute = beanClass.getAttributes()
                .findAllAttribute(name)
                .stream()
                .filter(attr -> attr instanceof OneToOneAssociation)
                .map(attr -> (OneToOneAssociation) attr)
                .findAny()
                .orElse(null);
        if (attribute == null) {
            attribute = new OneToOneAssociation();
            attribute.setConnectedClass(connectedClass);
            beanClass.getAttributes().addOneToOne(attribute);
            updateAttributeName(attribute, key);
        }
        return attribute;
    }

    protected OneToManyAssociation createOneToManyAssociation(BeanClass beanClass, BeanClass connectedClass, String key) {
        String name = firstLower(camelCase(key));
        OneToManyAssociation attribute = beanClass.getAttributes()
                .findAllAttribute(name)
                .stream()
                .filter(attr -> attr instanceof OneToManyAssociation)
                .map(attr -> (OneToManyAssociation) attr)
                .findAny()
                .orElse(null);
        if (attribute == null) {
            attribute = new OneToManyAssociation();
            attribute.setConnectedClass(connectedClass);
            beanClass.getAttributes().addOneToMany(attribute);
            updateAttributeName(attribute, key);
        }
        return attribute;
    }

    protected void updateAttributeName(Attribute attribute, String key) {
        if (attribute != null) {
            attribute.setId(getAutoGeneratedStringId());
            String name = firstLower(camelCase(key));
            if (jsonbSupport && !name.equalsIgnoreCase(key)) {
                attribute.setJsonbProperty(key);
            }
            if (jaxbSupport && !name.equalsIgnoreCase(key)) {
                attribute.getJaxbMetadata().setName(key);
            }
            attribute.setName(name);
        }
    }

    protected Basic createBasicAttribute(Entity entity, String attributeType, String key) {
        String name = firstLower(camelCase(key));
        Basic attribute = entity.getAttributes()
                .findAllAttribute(name)
                .stream()
                .filter(attr -> attr instanceof Basic)
                .map(attr -> (Basic) attr)
                .findAny()
                .orElse(null);
        if (attribute == null) {
            attribute = new Basic();
            attribute.setAttributeType(attributeType);
            entity.getAttributes().addBasic(attribute);
            if (jaxbSupport) {
                attribute.setJaxbVariableType(JaxbVariableType.XML_ATTRIBUTE);
            }
            updateAttributeName(attribute, name);
        }
        return attribute;
    }

    protected ElementCollection createElementCollection(Entity entity, String attributeType, String key) {
        String name = firstLower(camelCase(key));
        ElementCollection attribute = entity.getAttributes()
                .findAllAttribute(name)
                .stream()
                .filter(attr -> attr instanceof ElementCollection)
                .map(attr -> (ElementCollection) attr)
                .findAny()
                .orElse(null);
        if (attribute == null) {
            attribute = new ElementCollection();
            attribute.setTargetClass(attributeType);
            entity.getAttributes().addElementCollection(attribute);
            if (jaxbSupport) {
                attribute.setJaxbVariableType(JaxbVariableType.XML_ELEMENT);
            }
            updateAttributeName(attribute, name);
        }
        return attribute;
    }

    protected OneToOne createOneToOne(Entity entity, Entity connectedEntity, String key) {
        String name = firstLower(camelCase(key));
        OneToOne attribute = entity.getAttributes()
                .findAllAttribute(name)
                .stream()
                .filter(attr -> attr instanceof OneToOne)
                .map(attr -> (OneToOne) attr)
                .findAny()
                .orElse(null);
        if (attribute == null) {
            attribute = new OneToOne();
            attribute.setOwner(true);
            attribute.setConnectedEntity(connectedEntity);
            entity.getAttributes().addOneToOne(attribute);
            updateAttributeName(attribute, key);
        }
        return attribute;
    }

    protected OneToMany createOneToMany(Entity entity, Entity connectedEntity, String key) {
        String name = firstLower(camelCase(key));
        OneToMany attribute = entity.getAttributes()
                .findAllAttribute(name)
                .stream()
                .filter(attr -> attr instanceof OneToMany)
                .map(attr -> (OneToMany) attr)
                .findAny()
                .orElse(null);
        if (attribute == null) {
            attribute = new OneToMany();
            attribute.setOwner(true);
            attribute.setConnectedEntity(connectedEntity);
            entity.getAttributes().addOneToMany(attribute);
            updateAttributeName(attribute, key);
        }
        return attribute;
    }

}
